package com.pi.shisan.service.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.pi.shisan.service.audio.AudioLevelManager;
import com.pi.shisan.service.constants.ClockTypeEnum;
import com.pi.shisan.service.domain.AudioInfo;
import com.pi.shisan.service.domain.ClockInfo;
import com.pi.shisan.service.dto.AudioInfoDTO;
import com.pi.shisan.service.dto.ClockInfoDTO;
import com.pi.shisan.service.dto.Result;
import com.pi.shisan.service.dto.RunnableClockInfo;
import com.pi.shisan.service.exception.BusinessException;
import com.pi.shisan.service.mapper.AudioInfoMapper;
import com.pi.shisan.service.mapper.ClockInfoMapper;
import com.pi.shisan.service.service.ClockInfoService;
import com.pi.shisan.service.service.GovHolidayService;
import com.pi.shisan.service.util.CalcNextTimeRegister;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeSet;
import java.util.stream.Collectors;

import static com.pi.shisan.service.util.DateUtil.checkDay;

@Service
public class ClockInfoServiceImpl implements ClockInfoService {

    @Autowired
    private ClockInfoMapper clockInfoMapper;

    @Autowired
    private AudioInfoMapper audioInfoMapper;

    @Autowired
    private GovHolidayService govHolidayService;

    private TreeSet<RunnableClockInfo> PLANS = new AudioLevelManager.NoExceptionTree<>(Comparator.comparing(RunnableClockInfo::getNextTime));
    private Map<Long, RunnableClockInfo> TIME_MAP = new HashMap<>();

    @Override
    public Result<Long> createOrUpdateClock(ClockInfoDTO param) {
        boolean isCreate = param.getId() == null;
        if (param.getClkType() != null) {
            ClockTypeEnum clockType = ClockTypeEnum.getClockType(param.getClkType());
            if (clockType == null) {
                throw new BusinessException("闹铃类型不存在");
            }
            if (!checkYear(param.getClkYear())) {
                throw new BusinessException("年不合法");
            }
            if (!checkMonth(param.getClkMonth())) {
                throw new BusinessException("月不合法");
            }
            if (clockType == ClockTypeEnum.LUNAR_BIRTHDAY) {
                if (param.getClkDay() != null && (param.getClkDay() < 1 || param.getClkDay() > 30)) {
                    throw new BusinessException("日不合法");
                }
            } else {
                if (clockType !=  ClockTypeEnum.MOTHER_DAY && clockType != ClockTypeEnum.FATHER_DAY) {
                    if (!checkDay(param.getClkYear(), param.getClkMonth(), param.getClkDay())) {
                        throw new BusinessException("日不合法");
                    }
                }
            }
            if (!checkWeek(param.getClkWeek())) {
                throw new BusinessException("周不合法");
            }
            if (!checkHour(param.getClkHour())) {
                throw new BusinessException("小时不合法");
            }
            if (!checkMinute(param.getClkMinute())) {
                throw new BusinessException("分钟不合法");
            }
            if (clockType == ClockTypeEnum.BETWEEN_TIME && (param.getClkBetween() == null || param.getClkBetween() <= 0 || param.getClkBetween() > 9999)) {
                throw new BusinessException("间隔分钟数取值1 - 9999");
            }
            if (param.getClkWeek() != null && param.getClkDay() != null) {
                throw new BusinessException("周和日不可同时设置");
            }
            if (clockType == ClockTypeEnum.NO_REPEAT || clockType == ClockTypeEnum.BETWEEN_TIME) {
                if (param.getClkYear() == null || param.getClkMonth() == null || param.getClkDay() == null
                        || param.getClkHour() == null || param.getClkMinute() == null) {
                    throw new BusinessException("时间数据不足");
                }
                if (clockType == ClockTypeEnum.NO_REPEAT) {
                    LocalDateTime setTime = LocalDateTime.of(param.getClkYear(), param.getClkMonth(), param.getClkDay(), param.getClkHour(), param.getClkMinute());
                    if (setTime.isBefore(LocalDateTime.now())) {
                        throw new BusinessException("请将时间往后调");
                    }
                }
            } else if (clockType == ClockTypeEnum.LUNAR_BIRTHDAY) {
                if (param.getClkMonth() == null || param.getClkDay() == null
                        || param.getClkHour() == null || param.getClkMinute() == null) {
                    throw new BusinessException("时间数据不足");
                }
            }
        }

        ClockInfo clockInfo = new ClockInfo();
        BeanUtils.copyProperties(param, clockInfo);
        if (clockInfo.getId() != null) {
            clockInfoMapper.updateById(clockInfo);
            if (StringUtils.isNotBlank(clockInfo.getClkName())) {
                tryToUpdateClockNameFromPlan(clockInfo.getId(), clockInfo.getClkName());
            }
        } else {
            if (clockInfo.getClkName() == null || clockInfo.getClkName().trim().isEmpty()) {
                throw new BusinessException("请填写闹铃名称");
            }
            if (clockInfo.getClkType() == null) {
                clockInfo.setClkType(ClockTypeEnum.NO_REPEAT.getType());
                param.setClkType(clockInfo.getClkType());
            }
            clockInfoMapper.insert(clockInfo);
        }
        Long id = clockInfo.getId();
        if (param.getClkType() != null) {
            clockInfo = clockInfoMapper.selectById(clockInfo.getId());
            if (clockInfo.getClkWeek() != null && clockInfo.getClkDay() != null) {
                if (param.getClkWeek() == null) {
                    clockInfo.setClkWeek(null);
                    clockInfoMapper.clearWeekInfo(clockInfo.getId());
                } else {
                    clockInfo.setClkDay(null);
                    clockInfoMapper.clearDayInfo(clockInfo.getId());
                }
            }
            BeanUtils.copyProperties(clockInfo, param);
            try {
                if (!isCreate) {
                    if (audioInfoMapper.checkHashAudio(id) == null) {
                        calcNextTime(param);
                    } else {
                        addToPlan(param, calcNextTime(param));
                    }
                    clockInfoMapper.setToAvailableClock(id);
                }
            } catch (BusinessException e) {
                clockInfoMapper.setToUnavailableClock(id);
                if (Objects.equals(clockInfo.getClkSwitch(),1)) {
                    return Result.err(0,"闹铃已保存但是无法启动："+e.getMessage(),id);
                }
            }
        }
        return Result.ok(id);
    }

    @Override
    public synchronized void onOffClock(ClockInfoDTO param) throws Exception{
        param.setClkSwitch(Objects.equals(param.getClkSwitch(),1) ? 1 : 0);
        int succ = clockInfoMapper.onOffClock(param.getId(), param.getClkSwitch());
        if (succ > 0) {
            if (Objects.equals(param.getClkSwitch(), 1)) {
                Exception err = aloneCheckAndAddToPlanClockAvailable(param.getId());
                if (err != null) {
                    clockInfoMapper.onOffClock(param.getId(), 0);
                    throw err;
                }
            } else {
                tryToRemoveFromPlan(param.getId());
            }
        }
    }

    @Override
    public Long addOrUpdateClockAudio(AudioInfoDTO audioInfoDTO) {
        AudioInfo audioInfo = new AudioInfo();
        BeanUtils.copyProperties(audioInfoDTO, audioInfo);
        audioInfo.setClockId(audioInfoDTO.getClockId());
        if (audioInfo.getId() != null) {
            audioInfoMapper.updateById(audioInfo);
            audioInfo = audioInfoMapper.selectById(audioInfoDTO.getId());
        } else {
            audioInfoMapper.insert(audioInfo);
        }

        synchronized (this) {
            if (TIME_MAP.get(audioInfo.getClockId()) == null) {
                aloneCheckAndAddToPlanClockAvailable(audioInfo.getClockId());
            }
        }
        return audioInfo.getId();
    }

    @Override
    public void updateVolume(Long id, Integer volume) {
        clockInfoMapper.updateVolume(id,volume);
        synchronized (this) {
            RunnableClockInfo runnableClockInfo = TIME_MAP.get(id);
            if (runnableClockInfo != null && runnableClockInfo.getClockInfos() != null) {
                for (ClockInfoDTO c : runnableClockInfo.getClockInfos()) {
                    if (Objects.equals(c.getId(), id)) {
                        c.setClkVolume(volume);
                        break;
                    }
                }
            }
        }
    }

    @Override
    public void deleteClock(Long id) {
        audioInfoMapper.delete(
                new LambdaQueryWrapper<AudioInfo>()
                        .eq(AudioInfo::getClockId,id)
        );
        ClockInfo clockInfo = clockInfoMapper.selectById(id);
        int succ = clockInfoMapper.deleteById(id);
        if (succ != 1) {
            throw new BusinessException("删除失败，闹铃不存在");
        }
        if (clockInfo != null) {
            ClockInfoDTO clockInfoDTO = new ClockInfoDTO();
            BeanUtils.copyProperties(clockInfo, clockInfoDTO);
            try {
                tryToRemoveFromPlan(clockInfoDTO.getId());
            } catch (Exception e) {
            }
        }
    }

    //一般不会发生并发行为，除非你非得要搞事情，出现并发可能会导致音乐要么从计划中移除，要么没有移除
    @Override
    public void deleteAudio(Long id) {
        AudioInfo audioInfo = audioInfoMapper.selectById(id);
        if (audioInfo == null) {
            throw new BusinessException("删除失败，音频不存在");
        }
        int succ = audioInfoMapper.deleteById(id);
        if (succ != 1) {
            throw new BusinessException("删除失败，音频不存在");
        }
        if (audioInfoMapper.checkHashAudio(audioInfo.getClockId()) == null) {
            tryToRemoveFromPlan(audioInfo.getClockId());
        }
    }

    @Override
    public List<ClockInfoDTO> getClockList() {
        return getClockList(null);
    }

    @Override
    public ClockInfoDTO getClockById(Long id) {
        List<ClockInfoDTO> clockList = getClockList(Collections.singletonList(id));
        return clockList != null && clockList.size() > 0 ? clockList.get(0) :  null;
    }

    @Override
    public void initPlans() {
        long startId = 0;
        int limit = 1000;
        while (true) {
            List<ClockInfo> clockInfos = clockInfoMapper.selectForPage(startId, limit);
            if (clockInfos == null || clockInfos.isEmpty()) {
                break;
            }
            startId = clockInfos.get(clockInfos.size() - 1).getId();
            for (ClockInfo clockInfo : clockInfos) {
                ClockInfoDTO clockInfoDTO = new ClockInfoDTO();
                BeanUtils.copyProperties(clockInfo, clockInfoDTO);
                try {
                    addToPlan(clockInfoDTO, calcNextTime(clockInfoDTO));
                } catch (Exception e) {
                    clockInfoMapper.setToUnavailableClock(clockInfo.getId());
                }
            }
        }
    }

    @Override
    public void refreshChinaVacation() {
        if (govHolidayService.refresh()) {
            long startId = 0;
            int limit = 1000;
            while (true) {
                List<ClockInfo> clockInfos = clockInfoMapper.selectForPage(startId, limit);
                if (clockInfos == null || clockInfos.isEmpty()) {
                    break;
                }
                startId = clockInfos.get(clockInfos.size() - 1).getId();
                for (ClockInfo clockInfo : clockInfos) {
                    if (Objects.equals(clockInfo.getClkType(), ClockTypeEnum.CHINA_VACATION.getType()) || Objects.equals(clockInfo.getClkType(), ClockTypeEnum.CHINA_OFFICE.getType())) {
                        try {
                            ClockInfoDTO clockInfoDTO = new ClockInfoDTO();
                            BeanUtils.copyProperties(clockInfo, clockInfoDTO);
                            addToPlan(clockInfoDTO, calcNextTime(clockInfoDTO));
                            clockInfoMapper.setToAvailableClock(clockInfo.getId());
                        } catch (Exception e) {
                            clockInfoMapper.setToUnavailableClock(clockInfo.getId());
                        }
                    }
                }
            }
        }
    }

    @Override
    public synchronized RunnableClockInfo getNextClockInfo(boolean remove) {
        while (true) {
            RunnableClockInfo first = PLANS.first();
            if (first == null) {
                return null;
            }
            if (remove) {
                long currentTime = System.currentTimeMillis();
                if (first.getNextTime() <= currentTime) {
                    pollFirst();
                    if (first.getClockInfos() != null && first.getClockInfos().size() > 0) {
                        List<ClockInfoDTO> copiedList = new ArrayList<>(first.getClockInfos().size());
                        for (ClockInfoDTO clockInfoDTO : first.getClockInfos()) {
                            ClockInfoDTO copied = new ClockInfoDTO();
                            BeanUtils.copyProperties(clockInfoDTO, copied);
                            copiedList.add(copied);
                        }
                        RunnableClockInfo copiedRunnable = new RunnableClockInfo();
                        copiedRunnable.setNextTime(first.getNextTime());
                        copiedRunnable.setClockInfos(copiedList);
                        first = copiedRunnable;
                        for (ClockInfoDTO c : copiedList) {
                            try {
                                addToPlan(c, calcNextTime(c));
                            } catch (Exception e) {
                                clockInfoMapper.setToUnavailableClock(c.getId());
                            }
                        }
                    }
                } else {
                    first = null;
                }
            }
            if (first != null) {
                if (first.getClockInfos() == null || first.getClockInfos().isEmpty()) {
                    if (!remove) {
                        pollFirst();
                    }
                    continue;
                }
                List<Long> clkIds = first.getClockInfos().stream().map(ClockInfoDTO::getId).collect(Collectors.toList());
                List<AudioInfo> audioInfos = audioInfoMapper.selectList(new LambdaQueryWrapper<AudioInfo>()
                        .in(AudioInfo::getClockId, clkIds));

                Map<Long, List<AudioInfoDTO>> audioMap = null;
                if (audioInfos != null && audioInfos.size() > 0) {
                    audioMap = new HashMap<>(clkIds.size());
                    for (AudioInfo audioInfo : audioInfos) {
                        AudioInfoDTO audioInfoDTO = new AudioInfoDTO();
                        audioInfoDTO.setId(audioInfo.getId());
                        audioInfoDTO.setAudioName(audioInfo.getAudioName());
                        audioInfoDTO.setRepeatTimes(audioInfo.getRepeatTimes());
                        audioInfoDTO.setAudioPath(audioInfo.getAudioPath());
                        audioMap.computeIfAbsent(audioInfo.getClockId(), k->new ArrayList<>(2)).add(audioInfoDTO);
                    }
                }
                if (audioMap == null) {
                    audioMap = Collections.EMPTY_MAP;
                }

                List<ClockInfoDTO> clocks = new ArrayList<>(first.getClockInfos().size());
                for (ClockInfoDTO clockInfoDTO : first.getClockInfos()) {
                    List<AudioInfoDTO> list = audioMap.get(clockInfoDTO.getId());
                    if (list == null) {
                        continue;
                    }
                    ClockInfoDTO copied = new ClockInfoDTO();
                    BeanUtils.copyProperties(clockInfoDTO, copied);
                    copied.setAudios(list);
                    clocks.add(copied);
                }
                RunnableClockInfo runnableClockInfo = new RunnableClockInfo();
                runnableClockInfo.setNextTime(first.getNextTime());
                runnableClockInfo.setClockInfos(clocks);
                return runnableClockInfo;
            }
            break;
        }
        return null;
    }

    @Override
    public int clearHourInfo(Long id) {
        int succ = clockInfoMapper.clearHourInfo(id);
        if (succ > 0) {
            aloneCheckAndAddToPlanClockAvailable(id);
        }
        return succ;
    }

    @Override
    public int clearWeekInfo(Long id) {
        int succ = clockInfoMapper.clearWeekInfo(id);
        if (succ > 0) {
            aloneCheckAndAddToPlanClockAvailable(id);
        }
        return succ;
    }

    @Override
    public int clearDayInfo(Long id) {
        int succ = clockInfoMapper.clearDayInfo(id);
        if (succ > 0) {
            aloneCheckAndAddToPlanClockAvailable(id);
        }
        return succ;
    }

    @Override
    public int clearMonthInfo(Long id) {
        int succ = clockInfoMapper.clearMonthInfo(id);
        if (succ > 0) {
            aloneCheckAndAddToPlanClockAvailable(id);
        }
        return succ;
    }

    @Override
    public int clearYearInfo(Long id) {
        int succ = clockInfoMapper.clearYearInfo(id);
        if (succ > 0) {
            aloneCheckAndAddToPlanClockAvailable(id);
        }
        return succ;
    }

    private Exception aloneCheckAndAddToPlanClockAvailable(long id) {
        try {
            ClockInfo clockInfo = clockInfoMapper.selectById(id);
            ClockInfoDTO clockInfoDTO = new ClockInfoDTO();
            BeanUtils.copyProperties(clockInfo, clockInfoDTO);
            addToPlan(clockInfoDTO, calcNextTime(clockInfoDTO));
            clockInfoMapper.setToAvailableClock(id);
        } catch (BusinessException e) {
            clockInfoMapper.setToUnavailableClock(id);
            return e;
        }
        return null;
    }

    private RunnableClockInfo pollFirst() {
        RunnableClockInfo runnableClockInfo = PLANS.pollFirst();
        if (runnableClockInfo != null && runnableClockInfo.getClockInfos() != null) {
            for (ClockInfoDTO c : runnableClockInfo.getClockInfos()) {
                TIME_MAP.remove(c.getId());
            }
        }
        return runnableClockInfo;
    }

    public synchronized void tryToUpdateClockNameFromPlan(Long clockId, String name) {
        RunnableClockInfo runnableClockInfo = TIME_MAP.get(clockId);
        if (runnableClockInfo != null && runnableClockInfo.getClockInfos() != null) {
            for (ClockInfoDTO c : runnableClockInfo.getClockInfos()) {
                if (Objects.equals(c.getId(), clockId)) {
                    c.setClkName(name);
                    break;
                }
            }
        }
    }

    private List<ClockInfoDTO> getClockList(List<Long> ids) {
        List<ClockInfo> clockInfos = clockInfoMapper.selectList(
                new LambdaQueryWrapper<ClockInfo>()
                        .in(ids != null && ids.size() > 0, ClockInfo::getId, ids)
                        .orderByAsc(ClockInfo::getId)
        );
        if (clockInfos == null || clockInfos.isEmpty()) {
            return Collections.EMPTY_LIST;
        }
        List<Long> clockIds = clockInfos.stream().map(ClockInfo::getId).collect(Collectors.toList());
        List<AudioInfo> audioInfos = audioInfoMapper.selectList(
                new LambdaQueryWrapper<AudioInfo>()
                        .in(AudioInfo::getClockId, clockIds)
                        .orderByAsc(AudioInfo::getId)
        );
        Map<Long,List<AudioInfo>> clockIdToAudioMap = Collections.EMPTY_MAP;
        if (audioInfos != null && audioInfos.size() > 0) {
            clockIdToAudioMap = new HashMap<>(clockIds.size());
            for (AudioInfo audioInfo : audioInfos) {
                clockIdToAudioMap.computeIfAbsent(audioInfo.getClockId(), k -> new ArrayList<>(2)).add(audioInfo);
            }
        }
        List<ClockInfoDTO> clockList = new ArrayList<>(clockInfos.size());
        for (ClockInfo clockInfo : clockInfos) {
            ClockInfoDTO clockInfoDTO = new ClockInfoDTO();
            BeanUtils.copyProperties(clockInfo, clockInfoDTO);
            if (Objects.equals(clockInfoDTO.getClkType(), ClockTypeEnum.MOTHER_DAY.getType())
                    ||Objects.equals(clockInfoDTO.getClkType(), ClockTypeEnum.FATHER_DAY.getType())) {
                clockInfoDTO.setBeforeDay(clockInfo.getClkDay());
            } else if (Objects.equals(clockInfoDTO.getClkType(), ClockTypeEnum.LUNAR_BIRTHDAY.getType())) {
                clockInfoDTO.setLunarDay(clockInfo.getClkDay());
                clockInfoDTO.setLunarMonth(clockInfo.getClkMonth());
            }
            if (Objects.equals(clockInfoDTO.getClkSwitch(),1) && (
                    Objects.equals(clockInfoDTO.getClkType(), ClockTypeEnum.CHINA_OFFICE.getType())
                        || Objects.equals(clockInfoDTO.getClkType(), ClockTypeEnum.CHINA_VACATION.getType())
                        || Objects.equals(clockInfoDTO.getClkType(), ClockTypeEnum.MOTHER_DAY.getType())
                        ||Objects.equals(clockInfoDTO.getClkType(), ClockTypeEnum.FATHER_DAY.getType())
                        ||Objects.equals(clockInfoDTO.getClkType(), ClockTypeEnum.LUNAR_BIRTHDAY.getType()))
            ) {
                RunnableClockInfo runtime;
                synchronized (this) {
                    runtime = TIME_MAP.get(clockInfoDTO.getId());
                }
                if (runtime != null) {
                    LocalDateTime localDateTime = LocalDateTime.ofInstant(new Date(runtime.getNextTime()).toInstant(), ZoneId.systemDefault());
                    clockInfoDTO.setClkYear(localDateTime.getYear());
                    clockInfoDTO.setClkMonth(localDateTime.getMonthValue());
                    clockInfoDTO.setClkDay(localDateTime.getDayOfMonth());
                    if (runtime.getClockInfos() != null) {
                        for (ClockInfoDTO c : runtime.getClockInfos()) {
                            if (Objects.equals(c.getId(), clockInfoDTO.getId())) {
                                clockInfoDTO.setHolidayName(c.getHolidayName());
                                break;
                            }
                        }
                    }
                } else {
                    clockInfoDTO.setClkYear(null);
                    clockInfoDTO.setClkMonth(null);
                    clockInfoDTO.setClkDay(null);
                }
            }
            List<AudioInfo> thisAudioInfos = clockIdToAudioMap.get(clockInfo.getId());
            if (thisAudioInfos == null) {
                clockInfoDTO.setAudios(new ArrayList<>(0));
            } else {
                List<AudioInfoDTO> audioList = new ArrayList<>(thisAudioInfos.size());
                for (AudioInfo audioInfo : thisAudioInfos) {
                    AudioInfoDTO audioInfoDTO = new AudioInfoDTO();
                    BeanUtils.copyProperties(audioInfo, audioInfoDTO);
                    audioList.add(audioInfoDTO);
                }
                clockInfoDTO.setAudios(audioList);
            }
            clockList.add(clockInfoDTO);
        }
        return clockList;
    }

    private boolean checkMinute(Integer minute) {
        return minute == null || (minute >= 0 && minute <= 59);
    }

    private boolean checkHour(Integer hour) {
        return hour == null || (hour >= 0 && hour <= 23);
    }

    private boolean checkWeek(Integer week) {
        return week == null || (week >= 1 && week <= 7);
    }

    private boolean checkMonth(Integer month) {
        return month == null || (month >= 1 && month <= 12);
    }

    private boolean checkYear(Integer year) {
        return year == null || (year >= 2022 && year <= 2099);
    }

    private Long calcNextTime(ClockInfoDTO clockInfoDTO) {
        try {
            ClockTypeEnum clockType = ClockTypeEnum.getClockType(clockInfoDTO.getClkType());
            if (clockType == null) {
                throw new BusinessException("不支持的闹铃类型");
            }
            Integer minute = clockInfoDTO.getClkMinute();
            if (minute == null) {
                throw new BusinessException("至少要指定分钟");
            }
            return CalcNextTimeRegister.getFromMapping(clockType).calc(clockInfoDTO);
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
        }
    }


    private synchronized void tryToRemoveFromPlan(Long id) {
        RunnableClockInfo runnableClockInfo = TIME_MAP.get(id);
        TIME_MAP.remove(id);
        if (runnableClockInfo != null) {
            if (runnableClockInfo.getClockInfos() != null && runnableClockInfo.getClockInfos().size() > 0) {
                Iterator<ClockInfoDTO> iterator = runnableClockInfo.getClockInfos().iterator();
                while (iterator.hasNext()) {
                    ClockInfoDTO next = iterator.next();
                    if (Objects.equals(next.getId(), id)) {
                        iterator.remove();
                        break;
                    }
                }
                if (runnableClockInfo.getClockInfos().isEmpty()) {
                    PLANS.remove(runnableClockInfo);
//                    PLANS.removeIf(next -> next.getClockInfos() == null || next.getClockInfos().isEmpty());
                }
            }
        }
    }

    /**
     * 将闹铃加入执行队列
     * @param clockInfoDTO
     * @param nextTime 更新后的时间
     */
    private synchronized void addToPlan(ClockInfoDTO clockInfoDTO, Long nextTime) {
        tryToRemoveFromPlan(clockInfoDTO.getId());
        //第二步，加入
        if (nextTime == null || nextTime <= System.currentTimeMillis()) {
            return;
        }
        RunnableClockInfo runnableClockInfo = new RunnableClockInfo();
        runnableClockInfo.setNextTime(nextTime);
        RunnableClockInfo old = PLANS.ceiling(runnableClockInfo);
        if (old == null || old.getNextTime() / 60000L != nextTime / 60000L) {
            old = runnableClockInfo;
            PLANS.add(old);
        }
        TIME_MAP.put(clockInfoDTO.getId(), old);
        if (old.getClockInfos() == null) {
            old.setClockInfos(new ArrayList<>(1));
        }
        old.getClockInfos().add(clockInfoDTO);
    }
}
